package com.duojuhe.common.utils.excel;

import com.duojuhe.common.annotation.ExcelFormat;
import com.duojuhe.common.utils.dateutils.DateUtils;
import com.duojuhe.common.utils.httputils.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.*;

@Slf4j
public class ExportExcelUtil {

    /**
     * 导出excel
     * <p>
     * HttpServletResponse
     *
     * @param dataList       导出数据集合
     * @param excelFieldList key数组
     *                       文件名 HttpUtils.downLoadStream(workbook, response, request, fileName);
     */
    public static <T> void exportExcel(List<T> dataList, List<ExcelField> excelFieldList) {
        HttpServletResponse response = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse();
        if (response == null) {
            return;
        }
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        response.setContentType("application/octet-stream");//告诉浏览器输出内容为流
        OutputStream os = null;
        SXSSFWorkbook sxssfWorkbook = null;
        try {
            String title = DateUtils.dateToString(new Date(), DateUtils.yyyyMMddHHmmss) + ".xlsx";
            if ("IE".equals(HttpUtils.getBrowser(request))) {
                title = java.net.URLEncoder.encode(title, "UTF-8");
                response.setHeader("Content-Disposition", "attachment;filename=" + title);
            } else {
                title = new String(title.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
                response.setHeader("Content-Disposition", "attachment;filename=" + title);
            }
            response.setCharacterEncoding("utf-8");
            // 获取SXSSFWorkbook
            sxssfWorkbook = new SXSSFWorkbook(100);
            Sheet sheet = sxssfWorkbook.createSheet("Sheet1");
            // 创建样式
            //CellStyle style = sxssfWorkbook.createCellStyle();
            // 自定义时间格式
            // style.setDataFormat(sxssfWorkbook.createDataFormat().getFormat(DateUtils.DEFAULT_DATETIME_FORMAT));
            // 冻结第一行
            sheet.createFreezePane(0, 1);
            // 创建第一行,作为header表头
            Row header = sheet.createRow(0);
            // 循环创建header单元格
            for (int cellnum = 0; cellnum < excelFieldList.size(); cellnum++) {
                Cell cell = header.createCell(cellnum);
                //cell.setCellStyle(getAndSetXSSFCellStyleHeader(sxssfWorkbook));//设置表头单元格样式,根据需要设置
                cell.setCellValue(excelFieldList.get(cellnum).getFieldName());
                //设置每列固定宽度
                sheet.setColumnWidth(cellnum, 20 * 256);
            }
            // 遍历创建行,导出数据
            for (int rownum = 1; rownum <= dataList.size(); rownum++) {
                Row row = sheet.createRow(rownum);
                T target = dataList.get(rownum - 1);
                Map<String, Field> map = getFieldMapByClass(target);
                // 循环创建单元格
                for (int cellnum = 0; cellnum < excelFieldList.size(); cellnum++) {
                    Cell cell = row.createCell(cellnum);
                    //cell.setCellStyle(getAndSetXSSFCellStyleOne(sxssfWorkbook));//设置数据行单元格样式,根据需要设置
                    String fieldCellNum = excelFieldList.get(cellnum).getFieldCode();
                    if (target == null || map.size() == 0) {
                        cell.setCellValue("");
                    } else {
                        Object result = invokeGetter(target, fieldCellNum, map);
                        cell.setCellValue(result != null ? result.toString() : "");
                    }
                }
            }
            //自定义各列宽度
            //setSheet(sheet);
            os = response.getOutputStream();
            sxssfWorkbook.write(os);
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            try {
                if (sxssfWorkbook != null) {
                    //处理SXSSFWorkbook导出excel时，产生的临时文件
                    sxssfWorkbook.dispose();
                    sxssfWorkbook.close();
                }
                if (os != null) {
                    os.flush();
                    os.close();
                }
            } catch (IOException e) {
                log.error(e.getMessage());
            }
        }
    }

    /**
     * 反射调用指定对象属性的getter方法
     *
     * @param <T>       泛型
     * @param target    指定对象
     * @param fieldName 属性名
     * @return 返回调用后的值
     */
    private static <T> Object invokeGetter(T target, String fieldName, Map<String, Field> map) {
        try {
            // 如果属性名为xxx，则方法名为getXxx
            String methodName = firstCharUpperCase(fieldName);
            Method method = target.getClass().getMethod(methodName);
            Field field = map.get(fieldName);
            field.setAccessible(true);
            Object val = method.invoke(target);
            if (val == null) {
                return "";
            }
            Class<?> fieldType = field.getType();
            if (BigDecimal.class == fieldType) {
                val = ((BigDecimal) val).toPlainString();
            }
            //获取一个成员变量上的注解
            ExcelFormat format = field.getAnnotation(ExcelFormat.class);
            if (format != null) {
                if (Date.class == fieldType) {
                    val = DateUtils.dateToString((Date) val, format.dateFormat());
                }
            }
            return val;
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * 将指定字符串首字母转换成大写字母
     *
     * @param str 指定字符串
     * @return 返回首字母大写的字符串
     */
    private static String firstCharUpperCase(String str) {
        StringBuilder buffer = new StringBuilder(str);
        if (buffer.length() > 0) {
            char c = buffer.charAt(0);
            buffer.setCharAt(0, Character.toUpperCase(c));
        }
        return "get" + buffer.toString();
    }

    /**
     * 自定义各列宽度(单位为:字符宽度的1/256)
     */
    private static void setSheet(Sheet sheet) {
        sheet.setColumnWidth(0, 32 * 256);
        sheet.setColumnWidth(1, 32 * 256);
        sheet.setColumnWidth(2, 20 * 256);
        sheet.setColumnWidth(3, 20 * 256);
        sheet.setColumnWidth(4, 20 * 256);
        sheet.setColumnWidth(5, 20 * 256);
        sheet.setColumnWidth(6, 20 * 256);
        sheet.setColumnWidth(7, 20 * 256);
        sheet.setColumnWidth(8, 20 * 256);
        sheet.setColumnWidth(9, 20 * 256);
        sheet.setColumnWidth(10, 32 * 256);
    }


    /**
     * 根据属性名获取属性元素，包括各种安全范围和所有父类
     *
     * @param target
     * @return
     */
    private static Map<String, Field> getFieldMapByClass(Object target) {
        Map<String, Field> map = new HashMap<>();
        if (target == null) {
            return map;
        }
        //beg
        Class<?> clazz = target.getClass();
        for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
            Field[] field = clazz.getDeclaredFields();
            for (Field f : field) {
                map.put(f.getName(), f);
            }
        }
        return map;
    }

    /**
     * 获取并设置header样式
     */
    private static XSSFCellStyle getAndSetXSSFCellStyleHeader(SXSSFWorkbook sxssfWorkbook) {
        XSSFCellStyle xssfCellStyle = (XSSFCellStyle) sxssfWorkbook.createCellStyle();
        Font font = sxssfWorkbook.createFont();
        // 字体大小
        font.setFontHeightInPoints((short) 14);
        font.setFontName("楷体");
        // 将字体应用到样式上面
        xssfCellStyle.setFont(font);
        // 是否自动换行
        xssfCellStyle.setWrapText(false);
        // 水平居中
        xssfCellStyle.setAlignment(HorizontalAlignment.CENTER);
        // 垂直居中
        xssfCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        return xssfCellStyle;
    }
}
